/**
 * 云渲染引擎主文件
 * 使用 WebGL 2 实现实时云渲染
 */

'use strict';

// 导入着色器程序管理类
import ShaderProgram from './ShaderProgram.js';

// 主模块，使用立即执行函数表达式(IIFE)封装
var mainModule = (function () {
  // 调试信息对象，记录更新时间和帧时间
  let DBG_INFO = {
    updateTime: 0.,
    frameTime: 0.
  };

  // 相机参数
  let FOV = 60 * Math.PI / 180;  // 视场角
  let ZNEAR = .1;                // 近裁剪面
  let ZFAR = 10000.;             // 远裁剪面
  
  // 默认分辨率和移动速度
  const DEFAULT_RES = 256;
  const TRANSLATION_SPEED = .025;
  let translationIncrease = 1.;
  const ROTATION_SPEED = .007;

  // 各种纹理和体积数据的大小
  const WEATHER_MAP_SIZE = new Float32Array([256, 256]);
  const CLOUD_SHADOW_MAP_SIZE = new Float32Array([512, 512]);
  const CLOUD_SHADOW_MAP_VOLUME_SIZE = new Float32Array([32, 32, 32]);
  const CLOUD_DETAILS_VOLUME_SIZE = new Float32Array([64, 64, 64]);
  const SKY_VOLUME_SIZE = new Float32Array([32, 32, 32]);
  const SKY_OPTICAL_DEPTH_LUT_SIZE = new Float32Array([256, 64]);
  const SKY_VIEW_LUT_SIZE = new Float32Array([256, 144]);

  // 输入控制相关变量
  let PRESSED_KEYS = [];
  let MOUSE_MOVEMENTS = new Float32Array(96);
  let LAST_MOUSE_MOVEMENT_IDX = 0;

  // 键盘按键映射
  let KEYS = {
    F5: 116,
    F12: 123,
    F11: 122,
    ALT: 18,
    SPACEBAR: 32,
    SHIFT: 16,
    ENTER: 13,
    LEFT: 37,
    RIGHT: 39,
    UP: 38,
    DOWN: 40,
    Q: 81,
    E: 69,
    W: 87,
    S: 83,
    A: 65,
    D: 68,
    R: 82,
    F: 70,
    EQUALS: 187,
    MINUS: 189,
    NINE: 57,
    ZERO: 48
  };

  // 不需要阻止默认行为的按键
  let NO_PREVENT_DEFAULT_KEYS = new Uint8Array([KEYS.F5, KEYS.F12, KEYS.F11]);

  // 地球和大气层相关参数（用于参考和比例计算）
  const REarth = 6371;           // 地球半径
  const REarthAtmosphere = 6400; // 大气层半径
  const RCloud0Earth = 6372.;    // 云层底部半径
  const RCloud1Earth = 6374.;    // 云层顶部半径

  // 默认配置参数
  let DEFAULT_CFG = {
    _RPlanet: 123.,              // 行星半径
    _R1: 223,                    // 大气层半径
    _RCloud0: 126.89,            // 云层底部半径
    _RCloud1: 129.9,             // 云层顶部半径
    _cloudBaseDensityStart: .08, // 云层底部密度起始值
    _cloudTopDensityStart: .25,  // 云层顶部密度起始值
    _cloudTopDensityEnd: 1.,     // 云层顶部密度结束值
    _RSun: 123 * 1.,             // 太阳半径
    _cloudDensityMul: 14.,       // 云密度乘数
    _skyScatteringMul: 1.6,      // 天空散射乘数
    _skyAbsorptionMul: 1.6,      // 天空吸收乘数
    _mieScattering: new Float32Array([.003996, .003996, .003996]),  // 米氏散射系数
    _mieAbsorbtion: new Float32Array([.000444, .000444, .000444]),  // 米氏吸收系数
    _rayleighScattering: new Float32Array([.005802, .013558, .033100]),  // 瑞利散射系数
    _rayleighScatteringScale: 8.,  // 瑞利散射比例
    _mieScatteringScale: 1.2,      // 米氏散射比例
    _cloudScattering: new Float32Array([1., 1., 1.]),  // 云散射系数
    _lightPos: new Float32Array([0., 123 * 100., 123 * 100.]),  // 光源位置
    _lightMagnitude: 10,          // 光源强度
    _lightColor: new Float32Array([1., 1., 1.]),  // 光源颜色
    _cloudPhaseG0: -.2,           // 云相位函数参数0
    _cloudPhaseG1: .9,            // 云相位函数参数1
    _skyMarchSamples: 50.,        // 天空光线步进采样数
    _crepuscularRaysMarchSamples: 22.,  // 曙暮光光线步进采样数
    _cloudScattOctaves: 7,        // 云散射八度音阶数
    _skyScattOctaves: 7,          // 天空散射八度音阶数
    _cloudMarchSamples: 18.,      // 云光线步进采样数
    _cloudMarchMaxStepSize: .3399,  // 云光线最大步长
    _cloudShadowSamples: 10.,     // 云阴影采样数
    _weatherTexScale: .01,        // 天气纹理缩放
    _windMagnitude: 0.,           // 风速大小
    _cloudTexScale: .38,          // 云纹理缩放
    _erosionTexScale: 1.,         // 侵蚀纹理缩放
    _erosionThreshold: .31,       // 侵蚀阈值
    _temporalAlpha: .1,           // 时间混合系数
    _mouseSensivity: .002         // 鼠标灵敏度
  };

  let DEFAULT_CFG2 = {
    _RPlanet: 123.,
    _R1: 128.5,
    _RCloud0: 125.5,
    _RCloud1: 127.5,
    _cloudBaseDensityStart: .08,
    _cloudTopDensityStart: 0.25,
    _cloudTopDensityEnd: 1.,
    _RSun: 123. * 1.,
    _cloudDensityMul: 100.,
    _skyScatteringMul: 1.6,
    _skyAbsorptionMul: 1.6,
    _rayleighScattering: new Float32Array([.005802, .013558, .033100]),
    _cloudScattering: new Float32Array([1., 1., 1.]),
    _lightPos: new Float32Array([0., 123. * 100., 123 * 100.]),
    _lightMagnitude: 10,
    _lightColor: new Float32Array([1., 1., 1.]),
    _cloudPhaseG0: -.2,
    _cloudPhaseG1: .9,
    _skyMarchSamples: 30.,
    _cloudScattOctaves: 7,
    _skyScattOctaves: 7,
    _cloudMarchSamples: 27.,
    _weatherTexScale: .02,
    _cloudTexScale: 1.,
    _erosionTexScale: 1.,
    _erosionThreshold: .19,
    _temporalAlpha: .1,
    _mouseSensivity: .002
  };

  /**
   * 主模块类
   * 负责管理整个云渲染引擎的核心功能
   */
  class MainModule {
    /**
     * 构造函数
     * 初始化所有必要的变量和状态
     */
    constructor () {
      // WebGL 相关变量
      this._cnv = undefined;      // Canvas 元素
      this._gl = undefined;       // WebGL 上下文
      this._dt = 0;              // 时间增量

      // 渲染设置
      this._showDbgInfo = false;  // 是否显示调试信息
      this._hideUIOnBlur = false; // 失去焦点时是否隐藏UI
      this._bilateralBlurSigma = 3.5;  // 双边模糊的sigma值
      this._bilateralBlurBSigma = 2.0; // 双边模糊的b-sigma值
      this._marchCrepuscularRays = true;  // 是否进行曙暮光光线步进
      this._localShadowsVisibleDistScale = .25;  // 局部阴影可见距离比例
      this._globalShadowsVisibleDistScale = 1.;  // 全局阴影可见距离比例

      // 物理参数
      this._miePhaseG = .8;      // 米氏相位函数参数
      this._resolutionScale = 1.; // 分辨率缩放
      this._cloudShadowMapSamples = 30.;  // 云阴影图采样数
      this._skyShadowMapSamples = 50;     // 天空阴影图采样数
      this._shouldRecalcCloudShadowMap = true;  // 是否需要重新计算云阴影图
      this._shouldRecalcSkyShadowMap = true;    // 是否需要重新计算天空阴影图
      this._animateSun = false;   // 是否动画化太阳
      this._sampleCloudNoise = true;  // 是否采样云噪声
      this._checkCollisionWithPlanet = true;  // 是否检查与行星的碰撞
      this._timeModded = 0.;      // 修改后的时间
      this._prevTime = 0;         // 上一帧时间

      // 行星和云层参数
      this._RPlanet = 123.;       // 行星半径
      this._R1 = 223;            // 大气层半径
      this._RCloud0 = 126.89;    // 云层底部半径
      this._RCloud1 = 129.9;     // 云层顶部半径
      this._cloudBaseDensityStart = .08;  // 云层底部密度起始值
      this._cloudTopDensityStart = .25;   // 云层顶部密度起始值
      this._cloudTopDensityEnd = 1.;      // 云层顶部密度结束值
      this._RSun = this._RPlanet * 1.;    // 太阳半径
      this._cloudDensityMul = 14.;        // 云密度乘数
      this._skyScatteringMul = 1.6;       // 天空散射乘数
      this._skyAbsorptionMul = 1.6;       // 天空吸收乘数

      // 散射和吸收系数
      this._mieScattering = new Float32Array([.003996, .003996, .003996]);  // 米氏散射系数
      this._mieAbsorbtion = new Float32Array([.000444, .000444, .000444]);  // 米氏吸收系数
      this._rayleighScattering = new Float32Array([.005802, .013558, .033100]);  // 瑞利散射系数
      this._rayleighScatteringScale = 8.;  // 瑞利散射比例
      this._mieScatteringScale = 1.2;      // 米氏散射比例
      this._rayleighScatteringMagnitude = vec3.len(this._rayleighScattering);  // 瑞利散射幅度
      this._mieScatteringMagnitude = vec3.len(this._mieScattering);           // 米氏散射幅度
      this._mieAbsorbtionMagnitude = vec3.len(this._mieAbsorbtion);           // 米氏吸收幅度

      // 光源参数
      this._cloudScattering = new Float32Array([1., 1., 1.]);  // 云散射系数
      this._lightPos = new Float32Array([0., this._RPlanet * 100., this._RPlanet * 100.]);  // 光源位置
      this._lightMagnitude = 10;          // 光源强度
      this._lightColor = new Float32Array([1., 1., 1.]);  // 光源颜色

      // 渲染参数
      this._cloudPhaseG0 = -.2;           // 云相位函数参数0
      this._cloudPhaseG1 = .9;            // 云相位函数参数1
      this._skyMarchSamples = 50.;        // 天空光线步进采样数
      this._crepuscularRaysMarchSamples = 22.;  // 曙暮光光线步进采样数
      this._cloudScattOctaves = 7;        // 云散射八度音阶数
      this._skyScattOctaves = 7;          // 天空散射八度音阶数
      this._cloudMarchSamples = 18.;      // 云光线步进采样数
      this._cloudMarchMaxStepSize = .3399;  // 云光线最大步长
      this._cloudShadowSamples = 10.;     // 云阴影采样数
      this._weatherTexScale = .01;        // 天气纹理缩放
      this._windMagnitude = 0.;           // 风速大小
      this._cloudTexScale = .38;          // 云纹理缩放
      this._erosionTexScale = 1.;         // 侵蚀纹理缩放
      this._erosionThreshold = .31;       // 侵蚀阈值
      this._temporalAlpha = .1;           // 时间混合系数
      this._mouseSensivity = .002;        // 鼠标灵敏度

      // 矩阵和状态
      this._viewMat = undefined;          // 视图矩阵
      this._prevViewMat = undefined;      // 上一帧视图矩阵
      this._prevViewMatInv = undefined;   // 上一帧视图矩阵的逆
      this._projMat = undefined;          // 投影矩阵
      this._drawMode = undefined;         // 绘制模式
      this._windowSize = new Float32Array([0, 0]);  // 窗口大小
      this._mouse = new Float32Array([0, 0]);       // 鼠标位置

      // 缓冲区
      this._bufferVertices = undefined;    // 顶点缓冲区
      this._bufferIndices = undefined;     // 索引缓冲区
      this._bufferTextureCoords = undefined;  // 纹理坐标缓冲区

      // 着色器程序
      this._currentProgram = undefined;    // 当前着色器程序
      this._programs = {};                // 所有着色器程序
      this._binded = {};                  // 绑定的方法

      // 绑定所有必要的方法
      this._bindMethods([
        this.mainLoop,
        this._handleResize,
        this._handleMouse,
        this._handleMouseWheel,
        this._handleKeydown,
        this._handleKeyup,
        this._handleMousedown,
        this._handleMouseup,
        this._setupFinalRenderProgram,
        this._setupRender2DTexProgram,
        this._setupBlurProgram,
        this._setupDenoiseProgram,
        this._setupRenderAtmosphereProgram,
        this._setupRenderSkyVolumeProgram,
        this._setupRenderSkyShadowMapProgram,
        this._setupRenderCloudShadowMapsProgram,
        this._setupRenderCloudShadowMapVolumeProgram,
        this._setupRenderSkyViewLUTProgram,
        this._setupRenderWeatherMapProgram
      ]);
    }

    /**
     * 绑定方法到实例
     * 将方法绑定到当前实例，确保方法中的 this 指向正确
     * @param {Array} methods - 需要绑定的方法数组
     */
    _bindMethods (methods = []) {
      for (let i = 0; i < methods.length; i++) {
        const method = methods[i];
        this._binded[method.name] = method.bind(this);
      }
    }

    /**
     * 处理窗口大小改变事件
     * 更新 Canvas 尺寸和 WebGL 视口
     */
    _handleResize () {
      // 更新 Canvas 尺寸为窗口大小
      this._cnv.width = document.documentElement.clientWidth;
      this._cnv.height = document.documentElement.clientHeight;
      this._windowSize[0] = this._cnv.width;
      this._windowSize[1] = this._cnv.height;

      // 更新 WebGL 视口
      this._gl.viewport(0, 0, this._cnv.width, this._cnv.height);

      // 更新投影矩阵
      this._updateProjectionMatrix();
    }

    /**
     * 更新投影矩阵
     * 根据当前窗口大小和相机参数计算投影矩阵
     */
    _updateProjectionMatrix () {
      // 创建透视投影矩阵
      this._projMat = mat4.create();
      mat4.perspective(
        this._projMat,
        FOV,
        this._windowSize[0] / this._windowSize[1],
        ZNEAR,
        ZFAR
      );
    }

    /**
     * 计算观察矩阵
     * 根据相机位置和目标位置计算观察矩阵
     * @param {mat4} outMat - 输出的观察矩阵
     * @param {vec3} from - 相机位置
     * @param {vec3} to - 目标位置
     */
    _getLookAtMat (outMat, from, to) {
      // 计算相机上向量
      const up = vec3.fromValues(0, 1, 0);
      
      // 计算观察矩阵
      mat4.lookAt(outMat, from, to, up);
    }

    /**
     * 计算光线与球体的交点
     * 用于碰撞检测和光线追踪
     * @param {vec3} r0 - 光线起点
     * @param {vec3} rd - 光线方向
     * @param {vec3} s0 - 球体中心
     * @param {number} sR - 球体半径
     * @param {boolean} farthest - 是否返回最远的交点
     * @returns {number} 交点到光线起点的距离，如果没有交点返回 -1
     */
    _raySphereIntersect (r0, rd, s0, sR, farthest) {
      // 计算光线起点到球体中心的向量
      const r0s0 = vec3.create();
      vec3.sub(r0s0, r0, s0);

      // 计算二次方程的系数
      const a = vec3.dot(rd, rd);
      const b = 2 * vec3.dot(rd, r0s0);
      const c = vec3.dot(r0s0, r0s0) - sR * sR;

      // 计算判别式
      const d = b * b - 4 * a * c;
      if (d < 0) return -1;

      // 计算交点
      const t1 = (-b - Math.sqrt(d)) / (2 * a);
      const t2 = (-b + Math.sqrt(d)) / (2 * a);

      // 根据参数返回最近或最远的交点
      return farthest ? Math.max(t1, t2) : Math.min(t1, t2);
    }

    /**
     * 处理鼠标移动
     * 更新相机旋转
     */
    _processMouse () {
      // 如果指针被锁定，处理相机旋转
      if (document.pointerLockElement === this._cnv) {
        // 计算鼠标移动的累积效果
        let dx = 0, dy = 0;
        for (let i = 0; i < MOUSE_MOVEMENTS.length; i += 2) {
          dx += MOUSE_MOVEMENTS[i];
          dy += MOUSE_MOVEMENTS[i + 1];
        }

        // 应用旋转
        if (dx !== 0 || dy !== 0) {
          // 创建旋转矩阵
          const rotX = mat4.create();
          const rotY = mat4.create();
          
          // 绕Y轴旋转（水平）
          mat4.rotateY(rotY, rotY, -dx * ROTATION_SPEED);
          // 绕X轴旋转（垂直）
          mat4.rotateX(rotX, rotX, -dy * ROTATION_SPEED);
          
          // 组合旋转
          mat4.multiply(this._viewMat, rotY, this._viewMat);
          mat4.multiply(this._viewMat, rotX, this._viewMat);
        }
      }
    }

    /**
     * 处理鼠标按下事件
     * 请求指针锁定
     * @param {MouseEvent} e - 鼠标事件对象
     */
    _handleMousedown (e) {
      if (e.button === 0) {  // 左键点击
        this._cnv.requestPointerLock();
      }
    }

    /**
     * 处理鼠标释放事件
     * 退出指针锁定
     * @param {MouseEvent} e - 鼠标事件对象
     */
    _handleMouseup (e) {
      if (e.button === 0) {  // 左键释放
        document.exitPointerLock();
      }
    }

    /**
     * 处理鼠标滚轮事件
     * 调整相机移动速度
     * @param {WheelEvent} e - 滚轮事件对象
     */
    _handleMouseWheel (e) {
      // 根据滚轮方向调整移动速度
      translationIncrease += e.deltaY * 0.001;
      translationIncrease = Math.max(0.1, Math.min(10, translationIncrease));
    }

    /**
     * 处理按键按下事件
     * 记录按下的按键
     * @param {KeyboardEvent} e - 键盘事件对象
     */
    _handleKeydown (e) {
      // 如果按键不在白名单中，阻止默认行为
      if (!NO_PREVENT_DEFAULT_KEYS.includes(e.keyCode)) {
        e.preventDefault();
      }
      
      // 记录按下的按键
      if (!PRESSED_KEYS.includes(e.keyCode)) {
        PRESSED_KEYS.push(e.keyCode);
      }
    }

    /**
     * 处理按键释放事件
     * 移除释放的按键
     * @param {KeyboardEvent} e - 键盘事件对象
     */
    _handleKeyup (e) {
      // 从按下的按键列表中移除
      PRESSED_KEYS = PRESSED_KEYS.filter(key => key !== e.keyCode);
    }

    _processKeys () {
      // 计算移动方向
      const moveDir = vec3.create();
      
      // 前后移动
      if (PRESSED_KEYS.includes(KEYS.W)) moveDir[2] -= 1;
      if (PRESSED_KEYS.includes(KEYS.S)) moveDir[2] += 1;
      
      // 左右移动
      if (PRESSED_KEYS.includes(KEYS.A)) moveDir[0] -= 1;
      if (PRESSED_KEYS.includes(KEYS.D)) moveDir[0] += 1;
      
      // 上下移动
      if (PRESSED_KEYS.includes(KEYS.SPACEBAR)) moveDir[1] += 1;
      if (PRESSED_KEYS.includes(KEYS.SHIFT)) moveDir[1] -= 1;

      // 如果有移动，更新相机位置
      if (vec3.len(moveDir) > 0) {
        // 归一化移动方向
        vec3.normalize(moveDir, moveDir);
        
        // 计算移动距离
        const moveDist = TRANSLATION_SPEED * translationIncrease * this._dt;
        
        // 应用移动
        const moveVec = vec3.create();
        vec3.scale(moveVec, moveDir, moveDist);
        
        // 更新相机位置
        const pos = vec3.fromValues(
          this._viewMat[12],
          this._viewMat[13],
          this._viewMat[14]
        );
        vec3.add(pos, pos, moveVec);
        
        // 更新视图矩阵
        this._viewMat[12] = pos[0];
        this._viewMat[13] = pos[1];
        this._viewMat[14] = pos[2];
      }
    }

    /**
     * 设置纹理资源
     * 加载和管理纹理资源
     * @param {Array} texDescriptions - 纹理描述数组
     * @param {Function} cb - 加载完成后的回调函数
     */
    async _setupTextureAssets (texDescriptions = [], cb) {
      // 创建纹理对象数组
      const textures = [];
      
      // 加载每个纹理
      for (const desc of texDescriptions) {
        const texture = this._gl.createTexture();
        this._gl.bindTexture(desc.target, texture);
        
        // 设置纹理参数
        this._gl.texParameteri(desc.target, this._gl.TEXTURE_MIN_FILTER, desc.minFilter);
        this._gl.texParameteri(desc.target, this._gl.TEXTURE_MAG_FILTER, desc.magFilter);
        this._gl.texParameteri(desc.target, this._gl.TEXTURE_WRAP_S, desc.wrapS);
        this._gl.texParameteri(desc.target, this._gl.TEXTURE_WRAP_T, desc.wrapT);
        
        // 如果是3D纹理，设置额外的包装参数
        if (desc.target === this._gl.TEXTURE_3D) {
          this._gl.texParameteri(desc.target, this._gl.TEXTURE_WRAP_R, desc.wrapR);
        }
        
        // 加载纹理数据
        if (desc.data) {
          if (desc.target === this._gl.TEXTURE_2D) {
            this._gl.texImage2D(
              desc.target,
              0,
              desc.internalFormat,
              desc.width,
              desc.height,
              0,
              desc.format,
              desc.type,
              desc.data
            );
          } else if (desc.target === this._gl.TEXTURE_3D) {
            this._gl.texImage3D(
              desc.target,
              0,
              desc.internalFormat,
              desc.width,
              desc.height,
              desc.depth,
              0,
              desc.format,
              desc.type,
              desc.data
            );
          }
        }
        
        textures.push(texture);
      }
      
      // 调用回调函数
      if (cb) {
        cb(textures);
      }
      
      return textures;
    }

    _setCurrentProgram (shaderProgram) {
      // if (this._currentProgram === shaderProgram) {
      //   return;
      // }
      this._currentProgram = shaderProgram;
      this._gl.useProgram(shaderProgram.glProgram);
      shaderProgram.setup(this._gl);
      this._gl.uniform1f(shaderProgram.unifs.uT, performance.now());
      this._gl.uniform1f(shaderProgram.unifs.uTModded, this._timeModded);
      this._gl.uniform1f(shaderProgram.unifs.uDT, this._dt * .001);
      this._gl.uniform2fv(shaderProgram.unifs.uMouse, this._mouse);
      this._gl.uniform2fv(shaderProgram.unifs.uWindowSize, this._windowSize);
    }

    _setupFinalRenderProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1i(shaderProgram.unifs.uTex, 0);
      gl.uniform1i(shaderProgram.unifs.uTex3D, 1);
    }

    _setupRender2DTexProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1i(shaderProgram.unifs.uTex, 0);
    }

    _setupBlurProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1i(shaderProgram.unifs.uTex, 0);
      gl.uniform1f(shaderProgram.unifs.uSigma, this._bilateralBlurSigma);
      gl.uniform1f(shaderProgram.unifs.uBSigma, this._bilateralBlurBSigma);
    }

    _setupDenoiseProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1i(shaderProgram.unifs.uTex, 0);
    }

    _setupRenderSkyVolumeProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1f(shaderProgram.unifs.uSkyMarchSamples, this._skyMarchSamples);
      gl.uniform3fv(shaderProgram.unifs.uLightPos, this._lightPos);
      gl.uniform3fv(shaderProgram.unifs.uLightColor, this._lightColor);
      gl.uniform1f(shaderProgram.unifs.uLightMagnitude, this._lightMagnitude);
      gl.uniform1f(shaderProgram.unifs.uR1, this._R1);
      gl.uniform1f(shaderProgram.unifs.uRSun, this._RSun);
      gl.uniform1f(shaderProgram.unifs.uRPlanet, this._RPlanet);
      gl.uniform1f(shaderProgram.unifs.uRCloud0, this._RCloud0);
      gl.uniform1f(shaderProgram.unifs.uRCloud1, this._RCloud1);
      gl.uniform1i(shaderProgram.unifs.uSkyScattOctaves, this._skyScattOctaves);
      gl.uniform1f(shaderProgram.unifs.uSkyRayleighScatteringMul, this._skyScatteringMul);
      gl.uniform1f(shaderProgram.unifs.uSkyAbsorptionMul, this._skyAbsorptionMul);
      gl.uniform3fv(shaderProgram.unifs.uSkyRayleighScattering, this._rayleighScattering);
      gl.uniform3fv(shaderProgram.unifs.uMieScattering, this._mieScattering);
      gl.uniform3fv(shaderProgram.unifs.uMieAbsorbtion, this._mieAbsorbtion);
      gl.uniform1f(shaderProgram.unifs.uMiePhaseG, this._miePhaseG);
      gl.uniform1f(shaderProgram.unifs.uRayleighScaleDiv, this._rayleighScatteringScale);
      gl.uniform1f(shaderProgram.unifs.uMieScaleDiv, this._mieScatteringScale);
      gl.uniform1f(shaderProgram.unifs.uLastLayer, SKY_VOLUME_SIZE[2] - 1.);
      gl.uniformMatrix4fv(shaderProgram.unifs.uViewMat, false, this._viewMat);

      gl.uniform1i(shaderProgram.unifs.uSkyOpticalDepthToSun, 0);
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE0, this._skyOpticalDepthTex);
    }

    _setupRenderSkyViewLUTProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1f(shaderProgram.unifs.uSkyMarchSamples, this._skyMarchSamples);
      gl.uniform3fv(shaderProgram.unifs.uLightPos, this._lightPos);
      gl.uniform3fv(shaderProgram.unifs.uLightColor, this._lightColor);
      gl.uniform1f(shaderProgram.unifs.uLightMagnitude, this._lightMagnitude);
      gl.uniform1f(shaderProgram.unifs.uR1, this._R1);
      gl.uniform1f(shaderProgram.unifs.uRPlanet, this._RPlanet);
      gl.uniform1i(shaderProgram.unifs.uSkyScattOctaves, this._skyScattOctaves);
      gl.uniform1f(shaderProgram.unifs.uSkyRayleighScatteringMul, this._skyScatteringMul);
      gl.uniform1f(shaderProgram.unifs.uSkyAbsorptionMul, this._skyAbsorptionMul);
      gl.uniform3fv(shaderProgram.unifs.uSkyRayleighScattering, this._rayleighScattering);
      gl.uniform3fv(shaderProgram.unifs.uMieScattering, this._mieScattering);
      gl.uniform3fv(shaderProgram.unifs.uMieAbsorbtion, this._mieAbsorbtion);
      gl.uniform1f(shaderProgram.unifs.uMiePhaseG, this._miePhaseG);
      gl.uniform1f(shaderProgram.unifs.uRayleighScaleDiv, this._rayleighScatteringScale);
      gl.uniform1f(shaderProgram.unifs.uMieScaleDiv, this._mieScatteringScale);
      gl.uniform2fv(shaderProgram.unifs.uSkyViewLUTSize, SKY_VIEW_LUT_SIZE);
      gl.uniformMatrix4fv(shaderProgram.unifs.uViewMat, false, this._viewMat);

      gl.uniform1i(shaderProgram.unifs.uSkyOpticalDepthToSun, 0);
      // gl.uniform1i(shaderProgram.unifs.uSkyViewLUTBuffer, 1);
      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE0, this._skyOpticalDepthTex);
      // gl.activeTexture(gl.TEXTURE1);
      // gl.bindTexture(gl.TEXTURE_2D, this._skyViewLUTSwap);
    }

    _setupRenderSkyShadowMapProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);

      gl.uniform1f(shaderProgram.unifs.uCloudTexScale, this._cloudTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionTexScale, this._erosionTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionThreshold, this._erosionThreshold);
      gl.uniform1f(shaderProgram.unifs.uMarchSamples, this._skyShadowMapSamples);
      gl.uniform3fv(shaderProgram.unifs.uSkyRayleighScattering, this._rayleighScattering);
      gl.uniform3fv(shaderProgram.unifs.uCloudScattering, this._cloudScattering);
      gl.uniform3fv(shaderProgram.unifs.uLightPos, this._lightPos);
      gl.uniform1f(shaderProgram.unifs.uCloudDensityMul, this._cloudDensityMul);
      gl.uniform1f(shaderProgram.unifs.uSkyRayleighScatteringMul, this._skyScatteringMul);
      gl.uniform1f(shaderProgram.unifs.uSkyAbsorptionMul, this._skyAbsorptionMul);
      gl.uniform3fv(shaderProgram.unifs.uMieScattering, this._mieScattering);
      gl.uniform3fv(shaderProgram.unifs.uMieAbsorbtion, this._mieAbsorbtion);
      gl.uniform1f(shaderProgram.unifs.uRayleighScaleDiv, this._rayleighScatteringScale);
      gl.uniform1f(shaderProgram.unifs.uMieScaleDiv, this._mieScatteringScale);
      gl.uniform1f(shaderProgram.unifs.uRCloud0, this._RCloud0);
      gl.uniform1f(shaderProgram.unifs.uRCloud1, this._RCloud1);
      gl.uniform1f(shaderProgram.unifs.uR1, this._R1);
      gl.uniform1f(shaderProgram.unifs.uRSun, this._RSun);
      gl.uniform1f(shaderProgram.unifs.uRPlanet, this._RPlanet);
      gl.uniform1i(shaderProgram.unifs.uSampleCloudNoise, this._sampleCloudNoise);

      gl.uniform1i(shaderProgram.unifs.uCloudTex, 0);
      gl.uniform1i(shaderProgram.unifs.uErosionTex, 1);

      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE0, this._cloudVolumeTexture);
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE1, this._erosionVolumeTexture);
    }

    _setupRenderCloudShadowMapsProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);

      gl.uniform1f(shaderProgram.unifs.uWeatherTexScale, this._weatherTexScale);
      gl.uniform1f(shaderProgram.unifs.uWindMagnitude, this._windMagnitude);
      gl.uniform1f(shaderProgram.unifs.uCloudTexScale, this._cloudTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionTexScale, this._erosionTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionThreshold, this._erosionThreshold);
      gl.uniform1f(shaderProgram.unifs.uMarchSamples, this._cloudShadowMapSamples);
      gl.uniform3fv(shaderProgram.unifs.uCloudScattering, this._cloudScattering);
      gl.uniform3fv(shaderProgram.unifs.uLightPos, this._lightPos);
      gl.uniform1f(shaderProgram.unifs.uCloudDensityMul, this._cloudDensityMul);
      gl.uniform1f(shaderProgram.unifs.uRCloud0, this._RCloud0);
      gl.uniform1f(shaderProgram.unifs.uRCloud1, this._RCloud1);
      gl.uniform1f(shaderProgram.unifs.uCloudBaseDensityStart, this._cloudBaseDensityStart);
      gl.uniform1f(shaderProgram.unifs.uCloudTopDensityStart, this._cloudTopDensityStart);
      gl.uniform1f(shaderProgram.unifs.uCloudTopDensityEnd, this._cloudTopDensityEnd);
      gl.uniform1f(shaderProgram.unifs.uRSun, this._RSun);
      gl.uniform1f(shaderProgram.unifs.uRPlanet, this._RPlanet);
      gl.uniformMatrix4fv(shaderProgram.unifs.uObserverViewMat, false, this._viewMat);
      gl.uniformMatrix4fv(shaderProgram.unifs.uLocalShadowMapViewMat, false, this._localCloudShadowMapViewMat);
      gl.uniformMatrix4fv(shaderProgram.unifs.uGlobalShadowMapViewMat, false, this._globalCloudShadowMapViewMat);
      gl.uniform1i(shaderProgram.unifs.uSampleCloudNoise, this._sampleCloudNoise);
      gl.uniform1f(shaderProgram.unifs.uVisibleDist, this._visibleDist);
      gl.uniform1f(shaderProgram.unifs.uLocalShadowMapVisDistScale, this._localShadowsVisibleDistScale);
      gl.uniform1f(shaderProgram.unifs.uGlobalShadowMapVisDistScale, this._globalShadowsVisibleDistScale);

      gl.uniform1i(shaderProgram.unifs.uCloudTex, 0);
      gl.uniform1i(shaderProgram.unifs.uErosionTex, 1);
      gl.uniform1i(shaderProgram.unifs.uWeatherTex, 2);

      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_3D, this._cloudVolumeTexture);
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_3D, this._erosionVolumeTexture);
      gl.activeTexture(gl.TEXTURE2);
      gl.bindTexture(gl.TEXTURE_2D, this._weatherTexture);
    }

    _setupRenderCloudShadowMapVolumeProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);

      gl.uniform1f(shaderProgram.unifs.uWeatherTexScale, this._weatherTexScale);
      gl.uniform1f(shaderProgram.unifs.uWindMagnitude, this._windMagnitude);
      gl.uniform1f(shaderProgram.unifs.uCloudTexScale, this._cloudTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionTexScale, this._erosionTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionThreshold, this._erosionThreshold);
      gl.uniform1f(shaderProgram.unifs.uMarchSamples, this._cloudShadowMapSamples);
      gl.uniform3fv(shaderProgram.unifs.uCloudScattering, this._cloudScattering);
      gl.uniform3fv(shaderProgram.unifs.uLightPos, this._lightPos);
      gl.uniform1f(shaderProgram.unifs.uCloudDensityMul, this._cloudDensityMul);
      gl.uniform1f(shaderProgram.unifs.uRCloud0, this._RCloud0);
      gl.uniform1f(shaderProgram.unifs.uRCloud1, this._RCloud1);
      gl.uniform1f(shaderProgram.unifs.uCloudBaseDensityStart, this._cloudBaseDensityStart);
      gl.uniform1f(shaderProgram.unifs.uCloudTopDensityStart, this._cloudTopDensityStart);
      gl.uniform1f(shaderProgram.unifs.uCloudTopDensityEnd, this._cloudTopDensityEnd);
      gl.uniform1f(shaderProgram.unifs.uRSun, this._RSun);
      gl.uniform1f(shaderProgram.unifs.uRPlanet, this._RPlanet);
      gl.uniformMatrix4fv(shaderProgram.unifs.uObserverViewMat, false, this._viewMat);
      gl.uniformMatrix4fv(shaderProgram.unifs.uLocalShadowMapViewMat, false, this._localCloudShadowMapViewMat);
      gl.uniformMatrix4fv(shaderProgram.unifs.uGlobalShadowMapViewMat, false, this._globalCloudShadowMapViewMat);
      gl.uniform1i(shaderProgram.unifs.uSampleCloudNoise, this._sampleCloudNoise);
      gl.uniform1f(shaderProgram.unifs.uVisibleDist, this._visibleDist);
      gl.uniform1f(shaderProgram.unifs.uLocalShadowMapVisDistScale, this._localShadowsVisibleDistScale);
      gl.uniform1f(shaderProgram.unifs.uGlobalShadowMapVisDistScale, this._globalShadowsVisibleDistScale);
      gl.uniform1f(shaderProgram.unifs.uLastLayer, CLOUD_SHADOW_MAP_VOLUME_SIZE[2] - 1.);

      gl.uniform1i(shaderProgram.unifs.uCloudTex, 0);
      gl.uniform1i(shaderProgram.unifs.uErosionTex, 1);
      gl.uniform1i(shaderProgram.unifs.uWeatherTex, 2);

      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_3D, this._cloudVolumeTexture);
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_3D, this._erosionVolumeTexture);
      gl.activeTexture(gl.TEXTURE2);
      gl.bindTexture(gl.TEXTURE_2D, this._weatherTexture);
    }

    _setupRenderWeatherMapProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
    }

    _setupRenderAtmosphereProgram (gl, shaderProgram) {
      gl.enableVertexAttribArray(shaderProgram.attrs.aPos);
      gl.enableVertexAttribArray(shaderProgram.attrs.aTexCoord);
      gl.uniform1f(shaderProgram.unifs.uWeatherTexScale, this._weatherTexScale);
      gl.uniform1f(shaderProgram.unifs.uWindMagnitude, this._windMagnitude);
      gl.uniform1f(shaderProgram.unifs.uCloudTexScale, this._cloudTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionTexScale, this._erosionTexScale);
      gl.uniform1f(shaderProgram.unifs.uErosionThreshold, this._erosionThreshold);
      gl.uniform1f(shaderProgram.unifs.uTemporalAlpha, this._temporalAlpha);
      gl.uniform1f(shaderProgram.unifs.uCloudSamples, this._cloudMarchSamples);
      gl.uniform1f(shaderProgram.unifs.uCloudMarchMaxStepSize, this._cloudMarchMaxStepSize);
      gl.uniform1f(shaderProgram.unifs.uCloudShadowSamples, this._cloudShadowSamples);
      gl.uniform1f(shaderProgram.unifs.uSkyMarchSamples, this._skyMarchSamples);
      gl.uniform1f(shaderProgram.unifs.uCrepuscularRaysSamples, this._crepuscularRaysMarchSamples);
      gl.uniform3fv(shaderProgram.unifs.uMieScattering, this._mieScattering);
      gl.uniform3fv(shaderProgram.unifs.uMieAbsorbtion, this._mieAbsorbtion);
      gl.uniform3fv(shaderProgram.unifs.uSkyRayleighScattering, this._rayleighScattering);
      gl.uniform3fv(shaderProgram.unifs.uCloudScattering, this._cloudScattering);
      gl.uniform1i(shaderProgram.unifs.uCloudScattOctaves, this._cloudScattOctaves);
      gl.uniform1i(shaderProgram.unifs.uSkyScattOctaves, this._skyScattOctaves);
      gl.uniform3fv(shaderProgram.unifs.uLightPos, this._lightPos);
      gl.uniform1f(shaderProgram.unifs.uLightMagnitude, this._lightMagnitude);
      gl.uniform3fv(shaderProgram.unifs.uLightColor, this._lightColor);
      gl.uniform2fv(shaderProgram.unifs.uSkyViewLUTSize, SKY_VIEW_LUT_SIZE);
      gl.uniform1f(shaderProgram.unifs.uVisibleDist, this._visibleDist);
      gl.uniformMatrix4fv(shaderProgram.unifs.uViewMat, false, this._viewMat);
      gl.uniformMatrix4fv(shaderProgram.unifs.uPrevViewMat, false, this._prevViewMat);
      gl.uniformMatrix4fv(shaderProgram.unifs.uProjPrevViewMatInv, false, this._projPrevViewMatInv);
      gl.uniformMatrix4fv(shaderProgram.unifs.uLocalShadowMapProjViewMatInv, false, this._localCloudShadowMapProjViewMatInv);
      gl.uniformMatrix4fv(shaderProgram.unifs.uGlobalShadowMapProjViewMatInv, false, this._globalCloudShadowMapProjViewMatInv);

      gl.uniform1f(shaderProgram.unifs.uCloudPhaseG0, this._cloudPhaseG0);
      gl.uniform1f(shaderProgram.unifs.uCloudPhaseG1, this._cloudPhaseG1);
      gl.uniform1f(shaderProgram.unifs.uCloudDensityMul, this._cloudDensityMul);
      gl.uniform1f(shaderProgram.unifs.uSkyRayleighScatteringMul, this._skyScatteringMul);
      gl.uniform1f(shaderProgram.unifs.uSkyAbsorptionMul, this._skyAbsorptionMul);
      gl.uniform1f(shaderProgram.unifs.uRCloud0, this._RCloud0);
      gl.uniform1f(shaderProgram.unifs.uRCloud1, this._RCloud1);
      gl.uniform1f(shaderProgram.unifs.uCloudBaseDensityStart, this._cloudBaseDensityStart);
      gl.uniform1f(shaderProgram.unifs.uCloudTopDensityStart, this._cloudTopDensityStart);
      gl.uniform1f(shaderProgram.unifs.uCloudTopDensityEnd, this._cloudTopDensityEnd);
      gl.uniform1f(shaderProgram.unifs.uR1, this._R1);
      gl.uniform1f(shaderProgram.unifs.uRSun, this._RSun);
      gl.uniform1f(shaderProgram.unifs.uRPlanet, this._RPlanet);
      gl.uniform1i(shaderProgram.unifs.uIsMoving, this._isMoving);
      gl.uniform1i(shaderProgram.unifs.uSampleCloudNoise, this._sampleCloudNoise);
      gl.uniform1i(shaderProgram.unifs.uMarchCrepuscularRays, this._marchCrepuscularRays);

      gl.uniform1i(shaderProgram.unifs.uCloudTex, 0);
      gl.uniform1i(shaderProgram.unifs.uErosionTex, 1);
      gl.uniform1i(shaderProgram.unifs.uWeatherTex, 2);
      gl.uniform1i(shaderProgram.unifs.uCrepuscularRaysBuffer, 3);
      gl.uniform1i(shaderProgram.unifs.uMilkyWay, 4);
      gl.uniform1i(shaderProgram.unifs.uMieScattCloudTex, 5);
      gl.uniform1i(shaderProgram.unifs.uPrimaryCloudLayerBuffer, 6);
      gl.uniform1i(shaderProgram.unifs.uLocalCloudShadowMapTex, 7);
      gl.uniform1i(shaderProgram.unifs.uCloudShadowMapTex, 8);
      gl.uniform1i(shaderProgram.unifs.uSkyShadowMapTex, 9);
      gl.uniform1i(shaderProgram.unifs.uSkyScatteringVol, 10);
      gl.uniform1i(shaderProgram.unifs.uSkyTransmittanceVol, 11);
      gl.uniform1i(shaderProgram.unifs.uSkyViewLUT, 12);
      gl.uniform1i(shaderProgram.unifs.uPrimaryCloudLayerTransmittanceBuffer, 13);
      // gl.uniform1i(shaderProgram.unifs.uCloudShadowMapVol, 14);

      gl.activeTexture(gl.TEXTURE0);
      gl.bindTexture(gl.TEXTURE_3D, this._cloudVolumeTexture);
      gl.activeTexture(gl.TEXTURE1);
      gl.bindTexture(gl.TEXTURE_3D, this._erosionVolumeTexture);
      gl.activeTexture(gl.TEXTURE2);
      gl.bindTexture(gl.TEXTURE_2D, this._weatherTexture);
      gl.activeTexture(gl.TEXTURE3);
      gl.bindTexture(gl.TEXTURE_2D, this._crepuscularRaysBufferSwap);
      // gl.activeTexture(gl.TEXTURE4);
      // gl.bindTexture(gl.TEXTURE_2D, this._milkywayTexture);
      gl.activeTexture(gl.TEXTURE5);
      gl.bindTexture(gl.TEXTURE_2D, this._mieScatteringCloudTexture);
      gl.activeTexture(gl.TEXTURE6);
      gl.bindTexture(gl.TEXTURE_2D, this._primaryCloudLayerBufferSwap);
      gl.activeTexture(gl.TEXTURE7);
      gl.bindTexture(gl.TEXTURE_2D, this._localCloudShadowMap);
      gl.activeTexture(gl.TEXTURE8);
      gl.bindTexture(gl.TEXTURE_2D, this._globalCloudShadowMap);
      gl.activeTexture(gl.TEXTURE9);
      gl.bindTexture(gl.TEXTURE_2D, this._skyOpticalDepthTex);
      gl.activeTexture(gl.TEXTURE10);
      gl.bindTexture(gl.TEXTURE_3D, this._skyScatteringVolume);
      gl.activeTexture(gl.TEXTURE11);
      gl.bindTexture(gl.TEXTURE_3D, this._skyTransmittanceVolume);
      gl.activeTexture(gl.TEXTURE12);
      gl.bindTexture(gl.TEXTURE_2D, this._skyViewLUTSwap);
      gl.activeTexture(gl.TEXTURE13);
      gl.bindTexture(gl.TEXTURE_2D, this._primaryCloudLayerTransmittanceBufferSwap);
      // gl.activeTexture(gl.TEXTURE14);
      // gl.bindTexture(gl.TEXTURE_3D, this._cloudShadowMapVol);
    }

    /**
     * 设置默认混合状态
     * 配置 WebGL 的混合模式
     */
    _setDefaultBlendState () {
      // 启用混合
      this._gl.enable(this._gl.BLEND);
      
      // 设置混合函数
      this._gl.blendFunc(
        this._gl.SRC_ALPHA,
        this._gl.ONE_MINUS_SRC_ALPHA
      );
    }

    /**
     * 设置预乘混合状态
     * 用于处理预乘 alpha 的纹理
     */
    _setPreMultBlendState () {
      // 启用混合
      this._gl.enable(this._gl.BLEND);
      
      // 设置预乘混合函数
      this._gl.blendFunc(
        this._gl.ONE,
        this._gl.ONE_MINUS_SRC_ALPHA
      );
    }

    /**
     * 设置预乘 alpha 混合状态
     * 用于处理预乘 alpha 的纹理，但保持 alpha 通道
     */
    _setPreMultAlphaBlendState () {
      // 启用混合
      this._gl.enable(this._gl.BLEND);
      
      // 设置预乘 alpha 混合函数
      this._gl.blendFuncSeparate(
        this._gl.ONE,
        this._gl.ONE_MINUS_SRC_ALPHA,
        this._gl.ONE,
        this._gl.ONE_MINUS_SRC_ALPHA
      );
    }

    /**
     * 设置 WebGL 状态
     * 初始化 WebGL 上下文和基本状态
     */
    _setupGL () {
      // 设置视口
      this._gl.viewport(0, 0, this._cnv.width, this._cnv.height);
      
      // 启用深度测试
      this._gl.enable(this._gl.DEPTH_TEST);
      this._gl.depthFunc(this._gl.LEQUAL);
      
      // 启用背面剔除
      this._gl.enable(this._gl.CULL_FACE);
      this._gl.cullFace(this._gl.BACK);
      
      // 设置清除颜色
      this._gl.clearColor(0, 0, 0, 1);
      
      // 创建帧缓冲对象
      this._framebuffer = this._gl.createFramebuffer();
      
      // 设置默认混合状态
      this._setDefaultBlendState();
    }

    /**
     * 设置数据数组
     * 创建顶点、索引和纹理坐标缓冲区
     */
    _setupDataArrays () {
      // 创建顶点数据
      const vertices = new Float32Array([
        -1, -1, 0,
         1, -1, 0,
         1,  1, 0,
        -1,  1, 0
      ]);
      
      // 创建索引数据
      const indices = new Uint16Array([
        0, 1, 2,
        0, 2, 3
      ]);
      
      // 创建纹理坐标数据
      const textureCoords = new Float32Array([
        0, 0,
        1, 0,
        1, 1,
        0, 1
      ]);
      
      // 创建并绑定顶点缓冲区
      this._bufferVertices = this._gl.createBuffer();
      this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._bufferVertices);
      this._gl.bufferData(this._gl.ARRAY_BUFFER, vertices, this._gl.STATIC_DRAW);
      
      // 创建并绑定索引缓冲区
      this._bufferIndices = this._gl.createBuffer();
      this._gl.bindBuffer(this._gl.ELEMENT_ARRAY_BUFFER, this._bufferIndices);
      this._gl.bufferData(this._gl.ELEMENT_ARRAY_BUFFER, indices, this._gl.STATIC_DRAW);
      
      // 创建并绑定纹理坐标缓冲区
      this._bufferTextureCoords = this._gl.createBuffer();
      this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._bufferTextureCoords);
      this._gl.bufferData(this._gl.ARRAY_BUFFER, textureCoords, this._gl.STATIC_DRAW);
    }

    async _setupTextureAssets (texDescriptions = [], cb) {
      if (texDescriptions.length === 0) {
        cb();
        return;
      }
      let a = texDescriptions.length;
      let i;
      for (i = 0; i < texDescriptions.length; i++) {
        let texDescription = texDescriptions[i];
        let img = new Image();
        if (texDescription.target === this._gl.TEXTURE_2D) {
          img.onload = () => {
            let LOD = 0;
            let border = 0;
            let tex = this._gl.createTexture();
            this._gl.bindTexture(this._gl.TEXTURE_2D, tex);
            this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, texDescription.magFilter);
            this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, texDescription.minFilter);
            this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, texDescription.wrapS);
            this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, texDescription.wrapT);
            this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, true);
            this._gl.texImage2D(
              this._gl.TEXTURE_2D,
              LOD,
              texDescription.internalFormat,
              img.width,
              img.height,
              border,
              texDescription.texelFormat,
              texDescription.type,
              img
            );
            this._gl.pixelStorei(this._gl.UNPACK_FLIP_Y_WEBGL, false);
            this[texDescription.fieldName] = tex;
            a--;
            if (a === 0) {
              cb();
            }
          };
        } else if (texDescription.target === this._gl.TEXTURE_3D) {
          img.onload = () => {
            // webgl currently can't create 3d tex from image directly
            // using trick with drawing to 2d canvas to extract img bytes
            let cnv = document.createElement('canvas');
            cnv.width = img.width;
            cnv.height = img.height;
            let ctx = cnv.getContext('2d');
            ctx.drawImage(img, 0, 0);
            let imgData = ctx.getImageData(0, 0, img.width, img.height);

            let LOD = 0;
            let border = 0;
            let tex = this._gl.createTexture();
            this._gl.bindTexture(this._gl.TEXTURE_3D, tex);
            this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MAG_FILTER, texDescription.magFilter);
            this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MIN_FILTER, texDescription.minFilter);
            this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_S, texDescription.wrapS);
            this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_T, texDescription.wrapT);
            this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_R, texDescription.wrapR);
            this._gl.texImage3D(
              this._gl.TEXTURE_3D,
              LOD,
              texDescription.internalFormat,
              texDescription.volumeSize[0],
              texDescription.volumeSize[1],
              texDescription.volumeSize[2],
              border,
              texDescription.texelFormat,
              texDescription.type,
              imgData.data
            );
            this[texDescription.fieldName] = tex;
            a--;
            if (a === 0) {
              cb();
            }
          };
        }
        img.src = texDescription.path;
      }
    }

    _createResolutionDependentAssets () {
      let LOD = 0;
      let internalFormat = this._gl.RGBA16F;
      let border = 0;
      let texelFormat = this._gl.RGBA;

      this._gl.deleteTexture(this._backBuffer);
      this._backBuffer = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._backBuffer);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._crepuscularRaysBuffer);
      this._crepuscularRaysBuffer = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._crepuscularRaysBuffer);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._crepuscularRaysBufferSwap);
      this._crepuscularRaysBufferSwap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._crepuscularRaysBufferSwap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._primaryCloudLayerBuffer);
      this._primaryCloudLayerBuffer = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._primaryCloudLayerBuffer);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._primaryCloudLayerBufferSwap);
      this._primaryCloudLayerBufferSwap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._primaryCloudLayerBufferSwap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._primaryCloudLayerTransmittanceBuffer);
      this._primaryCloudLayerTransmittanceBuffer = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._primaryCloudLayerTransmittanceBuffer);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._primaryCloudLayerTransmittanceBufferSwap);
      this._primaryCloudLayerTransmittanceBufferSwap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._primaryCloudLayerTransmittanceBufferSwap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        this._windowSize[0] * this._resolutionScale,
        this._windowSize[1] * this._resolutionScale,
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );
    }

    _createResolutionIndependentAssets () {
      let LOD = 0;
      let internalFormat = this._gl.RGBA16F;
      let border = 0;
      let texelFormat = this._gl.RGBA;

      this._gl.deleteTexture(this._weatherMap);
      this._weatherMap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._weatherMap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.REPEAT);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.REPEAT);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        this._gl.R16F,
        WEATHER_MAP_SIZE[0],
        WEATHER_MAP_SIZE[1],
        border,
        this._gl.RED,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._globalCloudShadowMap);
      this._globalCloudShadowMap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._globalCloudShadowMap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        CLOUD_SHADOW_MAP_SIZE[0],
        CLOUD_SHADOW_MAP_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._globalCloudShadowMapSwap);
      this._globalCloudShadowMapSwap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._globalCloudShadowMapSwap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        CLOUD_SHADOW_MAP_SIZE[0],
        CLOUD_SHADOW_MAP_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._localCloudShadowMap);
      this._localCloudShadowMap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._localCloudShadowMap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        CLOUD_SHADOW_MAP_SIZE[0],
        CLOUD_SHADOW_MAP_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._localCloudShadowMapSwap);
      this._localCloudShadowMapSwap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._localCloudShadowMapSwap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        CLOUD_SHADOW_MAP_SIZE[0],
        CLOUD_SHADOW_MAP_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      // this._gl.deleteTexture(this._cloudShadowMapVol);
      // this._cloudShadowMapVol = this._gl.createTexture();
      // this._gl.bindTexture(this._gl.TEXTURE_3D, this._cloudShadowMapVol);
      // this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      // this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      // this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      // this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      // this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_R, this._gl.CLAMP_TO_EDGE);
      // this._gl.texImage3D(
      //   this._gl.TEXTURE_3D,
      //   0,
      //   internalFormat,
      //   CLOUD_SHADOW_MAP_VOLUME_SIZE[0],
      //   CLOUD_SHADOW_MAP_VOLUME_SIZE[1],
      //   CLOUD_SHADOW_MAP_VOLUME_SIZE[2],
      //   0,
      //   texelFormat,
      //   this._gl.FLOAT,
      //   null
      // );

      this._gl.deleteTexture(this._skyScatteringVolume);
      this._skyScatteringVolume = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_3D, this._skyScatteringVolume);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_R, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage3D(
        this._gl.TEXTURE_3D,
        0,
        internalFormat,
        SKY_VOLUME_SIZE[0],
        SKY_VOLUME_SIZE[1],
        SKY_VOLUME_SIZE[2],
        0,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._skyTransmittanceVolume);
      this._skyTransmittanceVolume = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_3D, this._skyTransmittanceVolume);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_3D, this._gl.TEXTURE_WRAP_R, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage3D(
        this._gl.TEXTURE_3D,
        0,
        internalFormat,
        SKY_VOLUME_SIZE[0],
        SKY_VOLUME_SIZE[1],
        SKY_VOLUME_SIZE[2],
        0,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._skyOpticalDepthTex);
      this._skyOpticalDepthTex = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._skyOpticalDepthTex);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        SKY_OPTICAL_DEPTH_LUT_SIZE[0],
        SKY_OPTICAL_DEPTH_LUT_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._skyViewLUT);
      this._skyViewLUT = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._skyViewLUT);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        SKY_VIEW_LUT_SIZE[0],
        SKY_VIEW_LUT_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );

      this._gl.deleteTexture(this._skyViewLUTSwap);
      this._skyViewLUTSwap = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, this._skyViewLUTSwap);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.CLAMP_TO_EDGE);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.CLAMP_TO_EDGE);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        SKY_VIEW_LUT_SIZE[0],
        SKY_VIEW_LUT_SIZE[1],
        border,
        texelFormat,
        this._gl.FLOAT,
        null
      );
    }

    _createMieScatteringTexture (scattData = []) {
      let LOD = 0;
      let internalFormat = this._gl.RGBA32F;
      let texelFormat = this._gl.RGBA;
      let border = 0;
      let texture;
      texture = this._gl.createTexture();
      this._gl.bindTexture(this._gl.TEXTURE_2D, texture);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MAG_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_MIN_FILTER, this._gl.LINEAR);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_S, this._gl.REPEAT);
      this._gl.texParameteri(this._gl.TEXTURE_2D, this._gl.TEXTURE_WRAP_T, this._gl.REPEAT);
      this._gl.texImage2D(
        this._gl.TEXTURE_2D,
        LOD,
        internalFormat,
        scattData.length / 4,
        1,
        border,
        texelFormat,
        this._gl.FLOAT,
        scattData
      );

      return texture;
    }

    _updateShadowMapMatrices () {
      let lightDir = vec3.normalize(vec3.create(), this._lightPos);
      let projectorOrigin = vec3.create();
      let observerPos = this._viewMat.slice(12, 15);
      projectorOrigin[0] = lightDir[0] * this._RCloud1;
      projectorOrigin[1] = lightDir[1] * this._RCloud1;
      projectorOrigin[2] = lightDir[2] * this._RCloud1;
      let observerRelativeToProjector = vec3.sub(vec3.create(), observerPos, projectorOrigin);
      let _lightDir = vec3.negate(vec3.create(), lightDir);
      let observerDot_LigtDir = vec3.dot(observerRelativeToProjector, _lightDir);
      let projectedObserverPos = vec3.create();
      projectedObserverPos[0] = observerRelativeToProjector[0] + lightDir[0] * observerDot_LigtDir;
      projectedObserverPos[1] = observerRelativeToProjector[1] + lightDir[1] * observerDot_LigtDir;
      projectedObserverPos[2] = observerRelativeToProjector[2] + lightDir[2] * observerDot_LigtDir;
      let projectedObserverDist = vec3.len(projectedObserverPos);
      let projectedObserverDir = vec3.normalize(vec3.create(), projectedObserverPos);
      let minDist = Math.min(projectedObserverDist, this._RCloud1);

      let offset = this._visibleDist * this._localShadowsVisibleDistScale;
      let cameraPos = vec3.create();
      cameraPos[0] = projectorOrigin[0] + projectedObserverDir[0] * minDist;
      cameraPos[1] = projectorOrigin[1] + projectedObserverDir[1] * minDist;
      cameraPos[2] = projectorOrigin[2] + projectedObserverDir[2] * minDist;
      this._getLookAtMat(this._localCloudShadowMapViewMat, projectorOrigin, new Float32Array(3));
      this._localCloudShadowMapViewMat.set(cameraPos, 12);
      mat4.invert(this._localCloudShadowMapViewMatInv, this._localCloudShadowMapViewMat);

      offset = this._visibleDist * this._globalShadowsVisibleDistScale;
      cameraPos[0] = projectorOrigin[0] + projectedObserverDir[0] * minDist;
      cameraPos[1] = projectorOrigin[1] + projectedObserverDir[1] * minDist;
      cameraPos[2] = projectorOrigin[2] + projectedObserverDir[2] * minDist;
      this._getLookAtMat(this._globalCloudShadowMapViewMat, projectorOrigin, new Float32Array(3));
      this._globalCloudShadowMapViewMat.set(cameraPos, 12);
      mat4.invert(this._globalCloudShadowMapViewMatInv, this._globalCloudShadowMapViewMat);

      let globalVisibility = this._visibleDist * this._globalShadowsVisibleDistScale;

      mat4.ortho(
        this._globalCloudShadowMapProjMat,
        -globalVisibility,
        globalVisibility,
        -globalVisibility,
        globalVisibility,
        -this._RCloud1,
        this._RCloud1
      );

      mat4.ortho(
        this._localCloudShadowMapProjMat,
        -this._visibleDist * this._localShadowsVisibleDistScale,
        this._visibleDist * this._localShadowsVisibleDistScale,
        -this._visibleDist * this._localShadowsVisibleDistScale,
        this._visibleDist * this._localShadowsVisibleDistScale,
        -this._RCloud1,
        this._RCloud1
      );

      mat4.mul(this._localCloudShadowMapProjViewMatInv, this._localCloudShadowMapProjMat, this._localCloudShadowMapViewMatInv);
      mat4.mul(this._globalCloudShadowMapProjViewMatInv, this._globalCloudShadowMapProjMat, this._globalCloudShadowMapViewMatInv);
    }

    _updateVisibleDist () {
      let h = vec3.len(this._viewMat.slice(12, 15));
      this._visibleDist = Math.min(
        this._RCloud1,
        Math.sqrt(h * h - this._RPlanet * this._RPlanet) + Math.sqrt(this._RCloud1 * this._RCloud1 - this._RPlanet * this._RPlanet)
      );
    }

    _resetFramebufferAttachments () {
      let i;
      let maxAttachemts = this._gl.getParameter(this._gl.MAX_COLOR_ATTACHMENTS);
      for (i = 0; i < maxAttachemts; i++) {
        this._gl.framebufferTexture2D(this._gl.FRAMEBUFFER, this._gl[`COLOR_ATTACHMENT${i}`], this._gl.TEXTURE_2D, null, 0);
      }
    }

    _setupRenderingToBufferTexture (viewportWidth = DEFAULT_RES, viewportHeight = DEFAULT_RES, framebuffer = this._framebuffer) {
      this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, framebuffer);
      this._gl.viewport(0, 0, viewportWidth, viewportHeight);
      this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._bufferVerticesBuf);
      this._gl.vertexAttribPointer(this._currentProgram.attrs.aPos, 3, this._gl.FLOAT, false, 0, 0);
      this._gl.bindBuffer(this._gl.ELEMENT_ARRAY_BUFFER, this._bufferIndicesBuf);
      this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._bufferTextureCoordBuf);
      this._gl.vertexAttribPointer(this._currentProgram.attrs.aTexCoord, 2, this._gl.FLOAT, false, 0, 0);
    }

    _renderSkyVolume () {
      this._setCurrentProgram(this._programs.renderSkyVolume);
      this._setupRenderingToBufferTexture(SKY_VOLUME_SIZE[0], SKY_VOLUME_SIZE[1], this._skyVolumeFramebuffer);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0, this._gl.COLOR_ATTACHMENT1]);
      let i;
      for (i = 0; i < SKY_VOLUME_SIZE[2]; i++) {
        this._gl.framebufferTextureLayer(
          this._gl.FRAMEBUFFER,
          this._gl.COLOR_ATTACHMENT0,
          this._skyScatteringVolume,
          0,
          i
        );
        this._gl.framebufferTextureLayer(
          this._gl.FRAMEBUFFER,
          this._gl.COLOR_ATTACHMENT1,
          this._skyTransmittanceVolume,
          0,
          i
        );
        this._gl.uniform1f(this._programs.renderSkyVolume.unifs.uRenderLayer, i);
        this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
      }
      this._resetFramebufferAttachments();
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0]);
    }

    _renderSkyViewLUT () {
      this._setCurrentProgram(this._programs.renderSkyViewLUT);
      this._setupRenderingToBufferTexture(SKY_VIEW_LUT_SIZE[0], SKY_VIEW_LUT_SIZE[1], this._skyViewLUTFramebuffer);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0]);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        this._skyViewLUT,
        0
      );
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);

      // bilateral blur as an attempt to hide stepping(banding) caused by small sample size on highly curved planets (when RPlanet ralatively small)
      this._blurTex(this._skyViewLUT, this._skyViewLUTSwap, SKY_VIEW_LUT_SIZE[0], SKY_VIEW_LUT_SIZE[1], this._skyViewLUTFramebuffer);
    }

    _renderSkyShadowMap () {
      if (!this._shouldRecalcSkyShadowMap) {
        return;
      }
      mat4.ortho(
        this._skyShadowMapProjMat,
        -this._R1,
        this._R1,
        -this._R1,
        this._R1,
        -this._R1,
        this._R1
      );
      // mat4.perspective(
      //   this._shadowMapProjMat,
      //   Math.atan(this._RCloud1 / vec3.len(this._lightPos)) * 2,
      //   1.,
      //   ZNEAR,
      //   ZFAR
      // );
      this._setCurrentProgram(this._programs.renderSkyShadowMap);
      this._setupRenderingToBufferTexture(SKY_OPTICAL_DEPTH_LUT_SIZE[0], SKY_OPTICAL_DEPTH_LUT_SIZE[1], this._skyOpticalDepthFramebuffer);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0]);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        this._skyOpticalDepthTex,
        0
      );
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);

      this._gl.bindTexture(this._gl.TEXTURE_2D, this._skyOpticalDepthTex);
      this._gl.generateMipmap(this._gl.TEXTURE_2D);


      // this._setupRenderingToBufferTexture(SKY_VOLUME_W, SKY_VOLUME_W);
      // let i;
      // for (i = 0; i < SKY_VOLUME_D; i++) {
      //   this._gl.framebufferTextureLayer(
      //     this._gl.FRAMEBUFFER,
      //     this._gl.COLOR_ATTACHMENT0,
      //     this._skyScatteringVolume,
      //     0,
      //     i
      //   );
      //   this._gl.uniform1f(this._programs.renderSkyShadowMap.unifs.uRenderLayer, i);
      //   this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
      // }
      this._shouldRecalcSkyShadowMap = false;
    }

    _renderCloudShadowMaps () {
      if (!this._shouldRecalcCloudShadowMap) {
        return;
      }
      this._updateShadowMapMatrices();
      this._setCurrentProgram(this._programs.renderCloudShadowMaps);
      this._setupRenderingToBufferTexture(CLOUD_SHADOW_MAP_SIZE[0], CLOUD_SHADOW_MAP_SIZE[1], this._cloudShadowmapFramebuffer);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0, this._gl.COLOR_ATTACHMENT1]);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        this._globalCloudShadowMap,
        0
      );
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT1,
        this._gl.TEXTURE_2D,
        this._localCloudShadowMap,
        0
      );
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0]);
      this._resetFramebufferAttachments();

      // this._gl.bindTexture(this._gl.TEXTURE_2D, this._localCloudShadowMap);
      // this._gl.generateMipmap(this._gl.TEXTURE_2D);
      this._shouldRecalcCloudShadowMap = false;
    }

    _renderCloudShadowMapVolume () {
      let i;
      this._setCurrentProgram(this._programs.renderCloudShadowMapVolume);
      this._setupRenderingToBufferTexture(CLOUD_SHADOW_MAP_VOLUME_SIZE[0], CLOUD_SHADOW_MAP_VOLUME_SIZE[1], this._cloudShadowMapVolFramebuffer);
      for (i = 0; i < CLOUD_SHADOW_MAP_VOLUME_SIZE[2]; i++) {
        this._gl.framebufferTextureLayer(
          this._gl.FRAMEBUFFER,
          this._gl.COLOR_ATTACHMENT0,
          this._cloudShadowMapVol,
          0,
          i
        );
        this._gl.uniform1f(this._programs.renderCloudShadowMapVolume.unifs.uRenderLayer, i);
        this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
      }
    }

    _renderWeatherMap () {
      this._setCurrentProgram(this._programs.renderWeatherMap);
      this._setupRenderingToBufferTexture(WEATHER_MAP_SIZE[0], WEATHER_MAP_SIZE[1], this._weatherMapFramebuffer);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        this._weatherMap,
        0
      );
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._gl.TRIANGLES, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
    }

    _renderAtmosphere () {
      this._setCurrentProgram(this._programs.renderAtmosphere);
      this._setupRenderingToBufferTexture(this._windowSize[0] * this._resolutionScale, this._windowSize[1] * this._resolutionScale, this._atmosphereFramebuffer);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        this._backBuffer,
        0
      );
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT1,
        this._gl.TEXTURE_2D,
        this._primaryCloudLayerBuffer,
        0
      );
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT2,
        this._gl.TEXTURE_2D,
        this._primaryCloudLayerTransmittanceBuffer,
        0
      );
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT3,
        this._gl.TEXTURE_2D,
        this._crepuscularRaysBuffer,
        0
      );
      this._gl.drawBuffers([
        this._gl.COLOR_ATTACHMENT0,
        this._gl.COLOR_ATTACHMENT1,
        this._gl.COLOR_ATTACHMENT2,
        this._gl.COLOR_ATTACHMENT3
      ]);
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0]);
      this._resetFramebufferAttachments();

      // this._blurTex(this._primaryCloudLayerBuffer, this._primaryCloudLayerBufferSwap, this._windowSize[0], this._windowSize[1], this._atmosphereFramebuffer);

      let swap;
      swap = this._primaryCloudLayerBuffer;
      this._primaryCloudLayerBuffer = this._primaryCloudLayerBufferSwap;
      this._primaryCloudLayerBufferSwap = swap;

      swap = this._crepuscularRaysBuffer;
      this._crepuscularRaysBuffer = this._crepuscularRaysBufferSwap;
      this._crepuscularRaysBufferSwap = swap;

      swap = this._primaryCloudLayerTransmittanceBuffer;
      this._primaryCloudLayerTransmittanceBuffer = this._primaryCloudLayerTransmittanceBufferSwap;
      this._primaryCloudLayerTransmittanceBufferSwap = swap;
    }

    _renderTex (tex, is3DTex = false) {
      this._setCurrentProgram(this._programs.finalRender);
      this._setupRenderingToScreen();
      if (is3DTex) {
        this._gl.activeTexture(this._gl.TEXTURE1);
        this._gl.bindTexture(this._gl.TEXTURE_3D, tex);
        this._gl.uniform1i(this._programs.finalRender.unifs.uRender3DTex, 1);
      } else {
        this._gl.activeTexture(this._gl.TEXTURE0);
        this._gl.bindTexture(this._gl.TEXTURE_2D, tex);
        this._gl.uniform1i(this._programs.finalRender.unifs.uRender3DTex, 0);
      }
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
    }

    _render2DTex (tex) {
      this._setCurrentProgram(this._programs.render2DTex);
      this._setupRenderingToScreen();
      this._gl.activeTexture(this._gl.TEXTURE0);
      this._gl.bindTexture(this._gl.TEXTURE_2D, tex);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
    }

    _blurTex (inTex, outTex, texWidth, texHeight, fbo) {
      this._setCurrentProgram(this._programs.blurTex);
      this._setupRenderingToBufferTexture(texWidth, texHeight, fbo);
      this._gl.drawBuffers([this._gl.COLOR_ATTACHMENT0]);
      this._gl.activeTexture(this._gl.TEXTURE0);
      this._gl.bindTexture(this._gl.TEXTURE_2D, inTex);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        outTex,
        0
      );
      this._gl.uniform2fv(this._currentProgram.unifs.uTexSize, new Float32Array([texWidth, texHeight]));
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
    }

    _denoise (inTex, outTex, texWidth, texHeight, fbo) {
      this._setCurrentProgram(this._programs.blurTex);
      this._setupRenderingToBufferTexture(texWidth, texHeight, fbo);
      this._gl.activeTexture(this._gl.TEXTURE0);
      this._gl.bindTexture(this._gl.TEXTURE_2D, inTex);
      this._gl.framebufferTexture2D(
        this._gl.FRAMEBUFFER,
        this._gl.COLOR_ATTACHMENT0,
        this._gl.TEXTURE_2D,
        outTex,
        0
      );
      // this._gl.uniform2fv(this._currentProgram.unifs.uTexSize, new Float32Array([texWidth, texHeight]));
      this._gl.clear(this._gl.COLOR_BUFFER_BIT | this._gl.DEPTH_BUFFER_BIT);
      this._gl.drawElements(this._drawMode, this._bufferIndices.length, this._gl.UNSIGNED_INT, 0);
    }

    _setupRenderingToScreen () {
      this._gl.clearColor(.0, .0, .0, 1.);
      this._gl.bindFramebuffer(this._gl.FRAMEBUFFER, null);
      this._gl.viewport(0, 0, this._windowSize[0], this._windowSize[1]);

      this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._bufferVerticesBuf);
      this._gl.vertexAttribPointer(this._currentProgram.attrs.aPos, 3, this._gl.FLOAT, false, 0, 0);
      this._gl.bindBuffer(this._gl.ELEMENT_ARRAY_BUFFER, this._bufferIndicesBuf);
      this._gl.bindBuffer(this._gl.ARRAY_BUFFER, this._bufferTextureCoordBuf);
      this._gl.vertexAttribPointer(this._currentProgram.attrs.aTexCoord, 2, this._gl.FLOAT, false, 0, 0);
    }

    _checkIfMoving () {
      let cameraPos = this._viewMat.slice(12,15);
      let prevCameraPos = this._prevViewMat.slice(12, 15);
      this._isMoving = cameraPos[0] != prevCameraPos[0] || cameraPos[1] != prevCameraPos[1] || cameraPos[2] != prevCameraPos[2];
    }

    _checkCameraPlanetCollision () {
      if (!this._checkCollisionWithPlanet) {
        return;
      }
      let cameraPos = this._viewMat.slice(12, 15);
      let len = vec3.len(cameraPos);
      // let prevCameraPos = this._prevViewMat.slice(12, 16);
      let v = vec3.create();
      // vec3.sub(v, prevCameraPos, cameraPos); // therefore actually -v
      vec3.normalize(v, cameraPos);
      if (len <= this._RPlanet) {
        let RPlanetDist = this._raySphereIntersect(cameraPos, v, [0, 0, 0], this._RPlanet, false) + .01;
        let newPos = vec3.create();
        let posShift = vec3.create();
        posShift[0] = v[0] * RPlanetDist;
        posShift[1] = v[1] * RPlanetDist;
        posShift[2] = v[2] * RPlanetDist;
        vec3.add(newPos, cameraPos, posShift);
        this._viewMat.set(newPos, 12);
      }
    }

    _dbgCalcReprojectedVals (resVec = new Float32Array([1., 1., 1., 1.])) {
      let rvCur = new Float32Array(4);
      rvCur.set(resVec, 0);

      resVec[0] = this._prevViewMatInv[0] * rvCur[0] + this._prevViewMatInv[4] * rvCur[1] + this._prevViewMatInv[8] * rvCur[2] + this._prevViewMatInv[12] * rvCur[3];
      resVec[1] = this._prevViewMatInv[1] * rvCur[0] + this._prevViewMatInv[5] * rvCur[1] + this._prevViewMatInv[9] * rvCur[2] + this._prevViewMatInv[13] * rvCur[3];
      resVec[2] = this._prevViewMatInv[2] * rvCur[0] + this._prevViewMatInv[6] * rvCur[1] + this._prevViewMatInv[10] * rvCur[2] + this._prevViewMatInv[14] * rvCur[3];
      resVec[3] = this._prevViewMatInv[3] * rvCur[0] + this._prevViewMatInv[7] * rvCur[1] + this._prevViewMatInv[11] * rvCur[2] + this._prevViewMatInv[15] * rvCur[3];

      rvCur.set(resVec, 0);

      resVec[0] = this._projMat[0] * rvCur[0] + this._projMat[4] * rvCur[1] + this._projMat[8] * rvCur[2] + this._projMat[12] * rvCur[3];
      resVec[1] = this._projMat[1] * rvCur[0] + this._projMat[5] * rvCur[1] + this._projMat[9] * rvCur[2] + this._projMat[13] * rvCur[3];
      resVec[2] = this._projMat[2] * rvCur[0] + this._projMat[6] * rvCur[1] + this._projMat[10] * rvCur[2] + this._projMat[14] * rvCur[3];
      resVec[3] = this._projMat[3] * rvCur[0] + this._projMat[7] * rvCur[1] + this._projMat[11] * rvCur[2] + this._projMat[15] * rvCur[3];

      let resVecPrspDiv = new Float32Array(4);
      resVecPrspDiv[0] = resVec[0] / resVec[3];
      resVecPrspDiv[1] = resVec[1] / resVec[3];
      resVecPrspDiv[2] = resVec[2] / resVec[3];

      console.log(resVec);
      console.log(resVecPrspDiv);
    }

    /**
     * 更新方法
     * 处理输入、更新状态和准备渲染
     */
    update () {
      // 计算时间增量
      const currentTime = performance.now();
      this._dt = (currentTime - this._prevTime) / 1000;
      this._prevTime = currentTime;

      // 更新修改后的时间
      this._timeModded += this._dt;

      // 处理输入
      this._processMouse();
      this._processKeys();

      // 检查相机与行星的碰撞
      if (this._checkCollisionWithPlanet) {
        this._checkCameraPlanetCollision();
      }

      // 检查是否需要重新计算阴影图
      if (this._checkIfMoving()) {
        this._shouldRecalcCloudShadowMap = true;
        this._shouldRecalcSkyShadowMap = true;
      }
    }

    /**
     * 渲染方法
     * 执行所有必要的渲染步骤
     */
    render () {
      // 渲染天空体积
      this._renderSkyVolume();
      
      // 渲染天空视图查找表
      this._renderSkyViewLUT();
      
      // 渲染天空阴影图
      this._renderSkyShadowMap();
      
      // 渲染云阴影图
      this._renderCloudShadowMaps();
      
      // 渲染天气图
      this._renderWeatherMap();
      
      // 渲染大气层
      this._renderAtmosphere();
    }

    /**
     * 主循环
     * 游戏循环的实现，处理更新和渲染
     */
    mainLoop () {
      // 更新状态
      this.update();
      
      // 执行渲染
      this.render();
      
      // 请求下一帧
      requestAnimationFrame(this._binded.mainLoop);
    }

    /**
     * 将十六进制颜色转换为RGB
     * @param {string} hex - 十六进制颜色字符串，如 '#FF0000'
     * @param {boolean} normalize - 是否将结果归一化到 [0,1] 范围
     * @returns {Float32Array} RGB颜色数组
     */
    _hexToRGB (hex = '#000000', normalize = false) {
      // 移除 # 符号
      hex = hex.replace('#', '');
      
      // 解析RGB值
      const r = parseInt(hex.substring(0, 2), 16);
      const g = parseInt(hex.substring(2, 4), 16);
      const b = parseInt(hex.substring(4, 6), 16);
      
      // 根据参数决定是否归一化
      if (normalize) {
        return new Float32Array([r / 255, g / 255, b / 255]);
      }
      return new Float32Array([r, g, b]);
    }

    /**
     * 将RGB颜色转换为十六进制
     * @param {Array} rgb - RGB颜色数组
     * @param {boolean} normalized - 输入是否已归一化到 [0,1] 范围
     * @returns {string} 十六进制颜色字符串
     */
    _RGBToHex (rgb = [], normalized = false) {
      // 确保输入是有效的RGB值
      const r = Math.max(0, Math.min(255, normalized ? rgb[0] * 255 : rgb[0]));
      const g = Math.max(0, Math.min(255, normalized ? rgb[1] * 255 : rgb[1]));
      const b = Math.max(0, Math.min(255, normalized ? rgb[2] * 255 : rgb[2]));
      
      // 转换为十六进制字符串
      const toHex = (n) => {
        const hex = Math.round(n).toString(16);
        return hex.length === 1 ? '0' + hex : hex;
      };
      
      return '#' + toHex(r) + toHex(g) + toHex(b);
    }

    /**
     * 应用模拟配置
     * 更新渲染参数
     * @param {Object} cfg - 配置对象
     */
    _applySimConfig (cfg) {
      // 更新行星和云层参数
      this._RPlanet = cfg._RPlanet;
      this._R1 = cfg._R1;
      this._RCloud0 = cfg._RCloud0;
      this._RCloud1 = cfg._RCloud1;
      this._cloudBaseDensityStart = cfg._cloudBaseDensityStart;
      this._cloudTopDensityStart = cfg._cloudTopDensityStart;
      this._cloudTopDensityEnd = cfg._cloudTopDensityEnd;
      this._RSun = cfg._RSun;
      this._cloudDensityMul = cfg._cloudDensityMul;
      this._skyScatteringMul = cfg._skyScatteringMul;
      this._skyAbsorptionMul = cfg._skyAbsorptionMul;

      // 更新散射和吸收系数
      this._mieScattering = cfg._mieScattering;
      this._mieAbsorbtion = cfg._mieAbsorbtion;
      this._rayleighScattering = cfg._rayleighScattering;
      this._rayleighScatteringScale = cfg._rayleighScatteringScale;
      this._mieScatteringScale = cfg._mieScatteringScale;

      // 更新光源参数
      this._lightPos = cfg._lightPos;
      this._lightMagnitude = cfg._lightMagnitude;
      this._lightColor = cfg._lightColor;

      // 更新渲染参数
      this._cloudPhaseG0 = cfg._cloudPhaseG0;
      this._cloudPhaseG1 = cfg._cloudPhaseG1;
      this._skyMarchSamples = cfg._skyMarchSamples;
      this._crepuscularRaysMarchSamples = cfg._crepuscularRaysMarchSamples;
      this._cloudScattOctaves = cfg._cloudScattOctaves;
      if (rgb.length < 3) {
        return '#000000';
      }
      let r = rgb[0];
      let g = rgb[1];
      let b = rgb[2];
      let hex;
      if (normalized) {
        r = Math.round(r * 255);
        g = Math.round(g * 255);
        b = Math.round(b * 255);
      }
      let rs = r.toString(16);
      let gs = g.toString(16);
      let bs = b.toString(16);
      if (rs.length < 2) {
        rs = `0${rs}`;
      }
      if (gs.length < 2) {
        gs = `0${gs}`;
      }
      if (bs.length < 2) {
        bs = `0${bs}`;
      }
      hex = `#${rs}${gs}${bs}`;
      return hex;
    }

    _applySimConfig (cfg) {
      let i;
      let j;
      let inputField;
      let idx;
      this._shouldRecalcCloudShadowMap = true;
      this._shouldRecalcSkyShadowMap = true;
      for (i in cfg) {
        inputField = document.querySelector(`[data-field="${i}"]`);
        if (inputField === null) {
          console.log('WARN: no input found with data-field=', i);
          continue;
        }
        idx = parseInt(inputField.dataset.idx, 10);
        if (inputField.type === 'number') {
          if (!Number.isNaN(idx)) {
            inputField.value = cfg[i][idx];
          } else {
            inputField.value = cfg[i];
          }
        } else if (inputField.type === 'color') {
          let colorVal = cfg[i];
          if (vec3.len(cfg[i]) <= 1.) {
            colorVal = vec3.normalize(new Float32Array(3), cfg[i]);
          }
          inputField.value = this._RGBToHex(colorVal, true);
        } else if (inputField.type === 'checkbox') {
          inputField.checked = cfg[i];
        }
        if (typeof cfg[i] === 'object' && cfg[i].length !== undefined) {
          if (this[i] !== undefined) {
            for (j = 0; j < this[i].length; j++) {
              this[i][j] = cfg[i][j];
            }
            continue;
          }
        }
        this[i] = cfg[i];
      }
    }

    // defining all ui related stuff here in order to easily move/remove in next projects
    // in this case it becomes self-contained plug-n-play thing.
    _addUI () {
      let html = `
        <div class="control-panel">
          <div class="section">Sun:</div>
          <div class="ui-item">
            <span class="caption">magnitude:</span>
            <input type="number"
              data-field="_lightMagnitude"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._lightMagnitude}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">radisu:</span>
            <input type="number"
              data-field="_RSun"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._RSun}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">X:</span>
            <input type="number"
              data-field="_lightPos"
              data-idx="0"
              data-recalc-shadow-map="true"
              step="50"
              class="narrow-64"
              value="${this._lightPos[0]}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">Y:</span>
            <input type="number"
              data-field="_lightPos"
              data-idx="1"
              data-recalc-shadow-map="true"
              step="50"
              class="narrow-64"
              value="${this._lightPos[1]}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">Z:</span>
            <input type="number"
              data-field="_lightPos"
              data-idx="2"
              data-recalc-shadow-map="true"
              step="50"
              class="narrow-64"
              value="${this._lightPos[2]}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">color:</span>
            <input type="color"
              data-field="_lightColor"
              class="narrow-64"
              value="${this._RGBToHex(this._lightColor, true)}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">animate sun:</span>
            <input type="checkbox"
              data-field="_animateSun"
              ${this._animateSun ? 'checked' : ''}
            />
          </div>

          <div class="section">Sky:</div>
          <div class="ui-item">
            <span class="caption">planet radius:</span>
            <input type="number"
              data-field="_RPlanet"
              data-recalc-sky-shadow-map="true"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._RPlanet}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">atmosphere radius:</span>
            <input type="number"
              data-field="_R1"
              data-recalc-sky-shadow-map="true"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._R1}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">rayleigh scattering:</span>
            <input type="color"
              data-field="_rayleighScattering"
              data-magnitude-scale="${this._rayleighScatteringMagnitude}"
              data-recalc-sky-shadow-map="true"
              class="narrow-64"
              value="${this._RGBToHex(vec3.normalize(new Float32Array(3), this._rayleighScattering), true)}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">rayleigh scattering mul:</span>
            <input type="number"
              data-field="_skyScatteringMul"
              data-recalc-sky-shadow-map="true"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._skyScatteringMul}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">ozone absorbtion mul:</span>
            <input type="number"
              data-field="_skyAbsorptionMul"
              data-recalc-sky-shadow-map="true"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._skyAbsorptionMul}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">rayleigh density scale 1/:</span>
            <input type="number"
              data-field="_rayleighScatteringScale"
              data-recalc-sky-shadow-map="true"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._rayleighScatteringScale}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">mie density scale 1/:</span>
            <input type="number"
              data-field="_mieScatteringScale"
              data-recalc-sky-shadow-map="true"
              data-val-min="0"
              step=".1"
              class="narrow-64"
              value="${this._mieScatteringScale}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">mie scattering:</span>
            <input type="color"
              data-field="_mieScattering"
              data-magnitude-scale="${this._mieScatteringMagnitude}"
              data-recalc-sky-shadow-map="true"
              class="narrow-64"
              value="${this._RGBToHex(vec3.normalize(new Float32Array(3), this._mieScattering), true)}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">mie absorbtion:</span>
            <input type="color"
              data-field="_mieAbsorbtion"
              data-magnitude-scale="${this._mieAbsorbtionMagnitude}"
              data-recalc-sky-shadow-map="true"
              class="narrow-64"
              value="${this._RGBToHex(vec3.normalize(new Float32Array(3), this._mieAbsorbtion), true)}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">mie phase G:</span>
            <input type="number"
              data-field="_miePhaseG"
              data-recalc-sky-shadow-map="true"
              class="narrow-64"
              step=".01"
              value="${this._miePhaseG}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">march samples:</span>
            <input type="number"
              data-field="_skyMarchSamples"
              data-val-min="1"
              step="1"
              class="narrow-64"
              value="${this._skyMarchSamples}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">scatt octaves:</span>
            <input type="number"
              data-field="_skyScattOctaves"
              data-val-min="0"
              step="1"
              class="narrow-64"
              value="${this._skyScattOctaves}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">optical depth map samples:</span>
            <input type="number"
              data-field="_skyShadowMapSamples"
              data-recalc-sky-shadow-map="true"
              data-val-min="1"
              step="1"
              class="narrow-64"
              value="${this._skyShadowMapSamples}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">bilateral blur sigma:</span>
            <input type="number"
              data-field="_bilateralBlurSigma"
              data-val-min="0."
              step=".1"
              class="narrow-64"
              value="${this._bilateralBlurSigma}"
            />
          </div>
          <div class="ui-item">
            <span class="caption">bilateral blur bsigma:</span>
            <input type="number"
              data-field="_bilateralBlurBSigma"
              data-val-min="0."
              step=".1"
              class="narrow-64"
              value="${this._bilateralBlurBSigma}"
            />
          </div>

          <div class="section">Clouds:</div>
          <div class="ui-item">
            <span class="caption">start radius:</span>
            <input type="number" data-field="_RCloud0" data-recalc-shadow-map="true" data-val-min="0" step=".1" class="narrow-64" value="${this._RCloud0}"/>
          </div>
          <div class="ui-item">
            <span class="caption">end radius:</span>
            <input type="number" data-field="_RCloud1" data-recalc-shadow-map="true" data-val-min="0" step=".1" class="narrow-64" value="${this._RCloud1}"/>
          </div>
          <div class="ui-item">
            <span class="caption">scattering:</span>
            <input type="color" data-field="_cloudScattering" data-recalc-shadow-map="true" class="narrow-64" value="${this._RGBToHex(this._cloudScattering, true)}"/>
          </div>
          <div class="ui-item">
            <span class="caption">density mul:</span>
            <input type="number" data-field="_cloudDensityMul" data-recalc-shadow-map="true" data-val-min="0" step="1" class="narrow-64" value="${this._cloudDensityMul}"/>
          </div>
          <div class="ui-item">
            <span class="caption">march samples:</span>
            <input type="number" data-field="_cloudMarchSamples" data-val-min="1" step=".5" class="narrow-64" value="${this._cloudMarchSamples}"/>
          </div>
          <div class="ui-item">
            <span class="caption">max stsepSize:</span>
            <input type="number" data-field="_cloudMarchMaxStepSize" data-val-min=".001" step=".01" class="narrow-64" value="${this._cloudMarchMaxStepSize}"/>
          </div>
          <div class="ui-item">
            <span class="caption">shadow march samples:</span>
            <input type="number" data-field="_cloudShadowSamples" data-val-min="1" step="1." class="narrow-64" value="${this._cloudShadowSamples}"/>
          </div>
          <div class="ui-item">
            <span class="caption">scatt octaves:</span>
            <input type="number" data-field="_cloudScattOctaves" data-val-min="0" step="1" class="narrow-64" value="${this._cloudScattOctaves}"/>
          </div>
          <div class="ui-item">
            <span class="caption">optical depth map samples:</span>
            <input type="number" data-field="_cloudShadowMapSamples" data-recalc-shadow-map="true" data-val-min="1" step="1" class="narrow-64" value="${this._cloudShadowMapSamples}"/>
          </div>
          <div class="ui-item">
            <span class="caption">phase g0:</span>
            <input type="number" data-field="_cloudPhaseG0" step=".05" class="narrow-64" value="${this._cloudPhaseG0}"/>
          </div>
          <div class="ui-item">
            <span class="caption">phase g1:</span>
            <input type="number" data-field="_cloudPhaseG1" data-val-min="0" step=".05" class="narrow-64" value="${this._cloudPhaseG1}"/>
          </div>
          <div class="ui-item">
            <span class="caption">base density start:</span>
            <input type="number" data-field="_cloudBaseDensityStart" data-recalc-shadow-map="true" data-val-min="0" data-val-max="1" step=".01" class="narrow-64" value="${this._cloudBaseDensityStart}"/>
          </div>
          <div class="ui-item">
            <span class="caption">top density start:</span>
            <input type="number" data-field="_cloudTopDensityStart" data-recalc-shadow-map="true" data-val-min="0" data-val-max="1" step=".01" class="narrow-64" value="${this._cloudTopDensityStart}"/>
          </div>
          <div class="ui-item">
            <span class="caption">top density end:</span>
            <input type="number" data-field="_cloudTopDensityEnd" data-recalc-shadow-map="true" data-val-min="0" data-val-max="1" step=".01" class="narrow-64" value="${this._cloudTopDensityEnd}"/>
          </div>
          <div class="ui-item">
            <span class="caption">erosion threshold:</span>
            <input type="number" data-field="_erosionThreshold" data-recalc-shadow-map="true" data-val-min="0" step="0.01" class="narrow-64" value="${this._erosionThreshold}"/>
          </div>
          <div class="ui-item">
            <span class="caption">cloud noise scale:</span>
            <input type="number" data-field="_cloudTexScale" data-recalc-shadow-map="true" data-val-min="0" step=".01" class="narrow-64" value="${this._cloudTexScale}"/>
          </div>
          <div class="ui-item">
            <span class="caption">weather tex scale:</span>
            <input type="number" data-field="_weatherTexScale" data-recalc-shadow-map="true" data-val-min="0" step=".01" class="narrow-64" value="${this._weatherTexScale}"/>
          </div>
          <div class="ui-item">
            <span class="caption">wind magnitude:</span>
            <input type="number"
              data-field="_windMagnitude"
              data-recalc-shadow-map="true"
              data-val-min=".0"
              step=".01"
              class="narrow-64"
              value="${this._windMagnitude}"
            />
          </div>

          <div class="section">Misc:</div>
          <div class="ui-item">
            <span class="caption">temporal coef:</span>
            <input type="number" data-field="_temporalAlpha" data-val-min="0" data-val-max="1" step=".05" class="narrow-64" value="${this._temporalAlpha}"/>
          </div>
          <div class="ui-item">
            <span class="caption">resolution scale:</span>
            <input type="number" data-field="_resolutionScale" data-val-min=".1" step=".1" data-reset-texture-assets="true" class="narrow-64" value="${this._resolutionScale}"/>
          </div>
          <div class="ui-item">
            <span class="caption">crepuscular rays samples:</span>
            <input type="number" data-field="_crepuscularRaysMarchSamples" data-val-min="1" step="1" class="narrow-64" value="${this._crepuscularRaysMarchSamples}"/>
          </div>
          <div class="ui-item">
            <span class="caption">local shadowmap visible dist:</span>
            <input type="number" data-field="_localShadowsVisibleDistScale" data-recalc-shadow-map="true" data-val-min="0." data-val-max="1." step=".01" class="narrow-64" value="${this._localShadowsVisibleDistScale}"/>
          </div>
          <div class="ui-item">
            <span class="caption">global shadowmap visible dist:</span>
            <input type="number" data-field="_globalShadowsVisibleDistScale" data-recalc-shadow-map="true" data-val-min="0." data-val-max="1." step=".01" class="narrow-64" value="${this._globalShadowsVisibleDistScale}"/>
          </div>
          <div class="ui-item">
            <span class="caption">collision with planet:</span><input type="checkbox" data-field="_checkCollisionWithPlanet" ${this._checkCollisionWithPlanet ? 'checked' : ''}/>
          </div>
          <div class="ui-item">
            <span class="caption">march crepuscular rays:</span><input type="checkbox" data-field="_marchCrepuscularRays" ${this._marchCrepuscularRays ? 'checked' : ''}/>
          </div>
          <div class="ui-item">
            <span class="caption">sample cloud noise:</span><input type="checkbox" data-field="_sampleCloudNoise" data-recalc-shadow-map="true" ${this._sampleCloudNoise ? 'checked' : ''}/>
          </div>
          <div class="ui-item">
            <span class="caption">show debug info:</span><input type="checkbox" data-field="_showDbgInfo" ${this._showDbgInfo ? 'checked' : ''}/>
          </div>
          <div class="ui-item">
            <span class="caption">hide ui when not in focus:</span>
            <input type="checkbox"
              data-field="_hideUIOnBlur"
              data-switch-class="hide-on-blur"
              ${this._hideUIOnBlur ? 'checked' : ''}
            />
          </div>
          <div class="ui-item reset-sim">
            <div class="button">reset</div>
          </div>
        </div>
      `;
      document.body.insertAdjacentHTML('beforeend', html);
      let cp = document.querySelector('.control-panel');
      cp.addEventListener('wheel', (e) => {
        e.stopPropagation();
      });
      cp.addEventListener('keydown', (e) => {
        e.stopPropagation();
      });
      cp.addEventListener('keyup', (e) => {
        e.stopPropagation();
      });

      let inputHandler = (e) => {
        let fieldName = e.target.dataset.field;
        if (this[fieldName] == undefined) {
          console.error('ERR: no fieldName', fieldName);
          return;
        }
        let idx = parseInt(e.target.dataset.idx, 10);
        let resetTextureAssets = e.target.dataset.resetTextureAssets === 'true';
        let recalcShadowMap = e.target.dataset.recalcShadowMap === 'true';
        let recalcSkyShadowMap = e.target.dataset.recalcSkyShadowMap === 'true';
        let minVal = parseFloat(e.target.dataset.valMin);
        let maxVal = parseFloat(e.target.dataset.valMax);
        if (Number.isNaN(minVal)) {
          minVal = -Infinity;
        }
        if (Number.isNaN(maxVal)) {
          maxVal = Infinity;
        }
        let val = Number.parseFloat(e.target.value);
        if (Number.isNaN(val)) {
          val = minVal;
        }
        val = Math.min(Math.max(minVal, val), maxVal);
        if (this[fieldName].length !== undefined) {
          if(Number.isNaN(idx) === false) {
            this[fieldName][idx] = val;
          } else {
            console.error('ERR: no idx was provided for array field', fieldName);
          }
        } else {
          this[fieldName] = val;
        }
        e.target.value = val;
        this._shouldRecalcCloudShadowMap = recalcShadowMap;
        this._shouldRecalcSkyShadowMap = recalcSkyShadowMap;
        if (resetTextureAssets) {
          this._createResolutionDependentAssets();
        }
      };

      let wheelHandler = (e) => {
        e.preventDefault();
        e.stopPropagation();
        let fieldName = e.target.dataset.field;
        if (this[fieldName] == undefined) {
          console.error('ERR: no fieldName', fieldName);
          return;
        }
        let idx = parseInt(e.target.dataset.idx, 10);
        let resetTextureAssets = e.target.dataset.resetTextureAssets === 'true';
        let recalcShadowMap = e.target.dataset.recalcShadowMap === 'true';
        let recalcSkyShadowMap = e.target.dataset.recalcSkyShadowMap === 'true';
        let val = parseFloat(e.target.value);
        let wheelStep = parseFloat(e.target.step);
        let minVal = parseFloat(e.target.dataset.valMin);
        let maxVal = parseFloat(e.target.dataset.valMax);
        if (Number.isNaN(minVal)) {
          minVal = -Infinity;
        }
        if (Number.isNaN(maxVal)) {
          maxVal = Infinity;
        }
        if (e.wheelDeltaY > 0) {
          val += wheelStep;
        } else {
          val -= wheelStep;
        }
        val = Math.min(Math.max(minVal, val), maxVal);
        if (this[fieldName].length !== undefined) {
          if(Number.isNaN(idx) === false) {
            this[fieldName][idx] = val;
          } else {
            console.error('ERR: no idx was provided for array field', fieldName);
          }
        } else {
          this[fieldName] = val;
        }
        e.target.value = val;
        this._shouldRecalcCloudShadowMap = recalcShadowMap;
        this._shouldRecalcSkyShadowMap = recalcSkyShadowMap;
        if (resetTextureAssets) {
          this._createResolutionDependentAssets();
        }
      };

      let colorInputHandler = (e) => {
        let fieldName = e.target.dataset.field;
        if (this[fieldName] == undefined) {
          console.error('ERR: no fieldName', fieldName);
          return;
        }
        let magnitudeScale = parseFloat(e.target.dataset.magnitudeScale);
        if (Number.isNaN(magnitudeScale)) {
          magnitudeScale = 1.;
        }
        let recalcShadowMap = e.target.dataset.recalcShadowMap === 'true';
        let recalcSkyShadowMap = e.target.dataset.recalcSkyShadowMap === 'true';
        let rgb = this._hexToRGB(e.target.value, true);
        rgb[0] = rgb[0] * magnitudeScale;
        rgb[1] = rgb[1] * magnitudeScale;
        rgb[2] = rgb[2] * magnitudeScale;
        this[fieldName].set(rgb, 0);
        this._shouldRecalcCloudShadowMap = recalcShadowMap;
        this._shouldRecalcSkyShadowMap = recalcSkyShadowMap;
      };

      let checkboxHandler = (e) => {
        let fieldName = e.target.dataset.field;
        if (this[fieldName] == undefined) {
          console.error('ERR: no fieldName', fieldName);
          return;
        }
        let classSwitch = e.target.dataset.switchClass;
        let recalcShadowMap = e.target.dataset.recalcShadowMap === 'true';
        let recalcSkyShadowMap = e.target.dataset.recalcSkyShadowMap === 'true';
        this[fieldName] = e.target.checked;
        this._shouldRecalcCloudShadowMap = recalcShadowMap;
        this._shouldRecalcSkyShadowMap = recalcSkyShadowMap;
        if (e.target.checked) {
          document.querySelector('.control-panel').classList.add(classSwitch);
        } else {
          document.querySelector('.control-panel').classList.remove(classSwitch);
        }
      };

      let i;
      let uiItems = document.querySelectorAll('.ui-item');
      let inputEl;
      for (i = 0; i < uiItems.length; i++) {
        inputEl = uiItems[i].querySelector('input');
        if (!inputEl) {
          continue;
        }
        if (inputEl.type === 'number') {
          inputEl.addEventListener('input', inputHandler);
          inputEl.addEventListener('wheel', wheelHandler);
        } else if (inputEl.type === 'color') {
          inputEl.addEventListener('input', colorInputHandler);
        } else if (inputEl.type === 'checkbox') {
          inputEl.addEventListener('change', checkboxHandler);
        }
      }

      let resetBtn = document.querySelector('.reset-sim');
      resetBtn.addEventListener('click', (e) => {
        this._applySimConfig(DEFAULT_CFG);
      });
    }

    _renderDbgInfo () {
      let dbgDOM = document.querySelector('.dbg');
      if (this._showDbgInfo) {
        dbgDOM.style.display = 'block';
      } else {
        dbgDOM.style.display = 'none';
        return;
      }
      let i;
      let dbgInfo = '';
      DBG_INFO.frameTime = this._dt;
      for (i in DBG_INFO) {
        dbgInfo += `${i}: ${DBG_INFO[i]}\n`;
      }
      dbgDOM.textContent = dbgInfo;
    }

    async _readMieScatteringData (filename = '') {
      return fetch(filename).then(resp => resp.text())
        .then(text => {
          let ai = text.indexOf('Angle');
          let dataWithHeader = text.substring(ai, text.length);
          let nli = dataWithHeader.indexOf('\n');
          let data = dataWithHeader.substring(nli + 1, text.length);
          let dataArr = data.split('\n');
          let min = Infinity, max = -Infinity;
          let len;
          let dataRow;
          let scattR, scattG, scattB;
          let scattVals = [];
          let i;
          for (i = 0; i < dataArr.length; i++) {
            dataRow = dataArr[i].split('\t');
            if (dataRow.length < 9){
              break;
            }
            scattR = parseFloat(dataRow[1].trim());
            scattG = parseFloat(dataRow[3].trim());
            scattB = parseFloat(dataRow[5].trim());
            min = Math.min(Math.min(Math.min(min, scattR), scattG), scattB);
            max = Math.max(Math.max(Math.max(max, scattR), scattG), scattB);
            scattVals.push(scattR, scattG, scattB, 1.);
          }

          // len = max - min;

          // for (i = 0; i < scattVals.length; i += 4) {
          //   scattVals[i] -= min;
          //   scattVals[i] /= len;
          //   scattVals[i + 1] -= min;
          //   scattVals[i + 1] /= len;
          //   scattVals[i + 2] -= min;
          //   scattVals[i + 2] /= len;

          //   scattVals[i] = Math.log(scattVals[i] * 10. + 1.);
          //   scattVals[i + 1] = Math.log(scattVals[i + 1] * 10. + 1.);
          //   scattVals[i + 2] = Math.log(scattVals[i + 2] * 10. + 1.);
          // }

          return new Float32Array(scattVals);
        });
    }
  }

  return new MainModule();
})();

window.onload = function () {
  mainModule.init();
  //dbg
  window.mainMod = mainModule;
  //
};
